import Layout from '../../components/Layout';
export default Layout;

import 'tippy.js/dist/tippy.css';
import 'tippy.js/themes/light-border.css';

# Popover

What we'll be building:

<div className="grid place-items-center bg-gray-100 rounded px-4 py-16 text-gray-900 mb-4">
  <Tippy
    content={
      <>
        <div className="text-left">
          <div className="text-xl font-bold p-2">
            My popover title
          </div>
          <div className="h-px bg-gray-200"></div>
          <p className="p-2">
            My long popover description that spans over multiple
            lines.
          </p>
        </div>
      </>
    }
    theme="light-border"
    interactive
    appendTo={() => document.body}
    trigger="click"
    aria={{content: 'labelledby'}}
  >
    <button className="bg-blue-600 rounded px-2 py-1 text-gray-50">
      Click to open popover
    </button>
  </Tippy>
</div>

## Basics

With a button and your popover template on your document, like
so:

```html
<button id="myButton">Trigger</button>
<div id="myPopover" style="display: none;">
  <h5>My popover title</h5>
  <hr />
  <p>My popover description that spans over multiple lines.</p>
</div>
```

Call `tippy()` and pass it the built-in `render` function and the
popover element as the content:

```js
import tippy, {render} from 'tippy.js';

tippy('#myButton', {
  render,
  content: document.querySelector('#myPopover'),
});
```

The result so far:

<div className="grid place-items-center bg-gray-100 rounded px-4 py-16 text-gray-900 mb-4">
  <Tippy
    animation={false}
    render={(attrs) => (
      <div {...attrs} className="text-gray-900">
        <h5 className="text-xl font-bold">My popover title</h5>
        <div className="h-px bg-gray-500" />
        <p>My popover content that spans over multiple lines</p>
      </div>
    )}
  >
    <button className="bg-blue-600 rounded px-2 py-1 text-gray-50">
      Trigger
    </button>
  </Tippy>
</div>

By default, the popover is positioned above the element if
there's enough space, and shows it on hover or focus.

## Styling

You can style the popover from scratch or leverage the package's
built-in themes.

```js {2,7}
import tippy, {render} from 'tippy.js';
import 'tippy.js/themes/light-border.css';

tippy('#myButton', {
  render,
  content: document.querySelector('#myPopover'),
  theme: 'light-border',
});
```

<div className="grid place-items-center bg-gray-100 rounded px-4 py-16 text-gray-900 mb-4">
  <Tippy
    animation={false}
    content={
      <div className="text-gray-900 bg-gray-50">
        <h5 className="text-xl font-bold my-2">
          My popover title
        </h5>
        <div className="h-px bg-gray-500" />
        <p className="my-2">
          My popover content that spans over multiple lines
        </p>
      </div>
    }
    theme="light-border"
  >
    <button className="bg-blue-600 rounded px-2 py-1 text-gray-50">
      Trigger
    </button>
  </Tippy>
</div>

## Animation

Next, we can add an opacity transition animation (or fade):

```js {1,3,9-10}
import tippy, {render, animation} from 'tippy.js';
import 'tippy.js/themes/light-border.css';
import 'tippy.js/animations/fade.css';

tippy('#myButton', {
  render,
  content: document.querySelector('#myPopover'),
  theme: 'light-border',
  animation: 'fade',
  middleware: [animation],
});
```

<div className="grid place-items-center bg-gray-100 rounded px-4 py-16 text-gray-900 mb-4">
  <Tippy
    content={
      <div className="text-gray-900 bg-gray-50">
        <h5 className="text-xl font-bold my-2">
          My popover title
        </h5>
        <div className="h-px bg-gray-500" />
        <p className="my-2">
          My popover content that spans over multiple lines
        </p>
      </div>
    }
    theme="light-border"
  >
    <button className="bg-blue-600 rounded px-2 py-1 text-gray-50">
      Trigger
    </button>
  </Tippy>
</div>

## Trigger event

To show the popover only upon clicking the target, pass
`trigger`:

```js {10}
import tippy, {render, animation, aria, dismiss} from 'tippy.js';
import 'tippy.js/themes/light-border.css';
import 'tippy.js/animations/fade.css';

tippy('#myButton', {
  render,
  content: document.querySelector('#myPopover'),
  theme: 'light-border',
  animation: 'fade',
  trigger: 'click',
  middleware: [animation, aria, dismiss],
});
```

## Accessibility

For users to be able to select text inside the popover:

```js {12}
import tippy, {render, animation, aria, dismiss} from 'tippy.js';
import 'tippy.js/themes/light-border.css';
import 'tippy.js/animations/fade.css';

tippy('#myButton', {
  render,
  content: document.querySelector('#myPopover'),
  theme: 'light-border',
  animation: 'fade',
  trigger: 'click',
  middleware: [animation, aria, dismiss],
  interactive: true,
});
```

To enable screen reader support, and the ability to dismiss the
popover via `esc`, import the `aria` and `dismiss` middleware:

```js {1,11}
import tippy, {render, animation, aria, dismiss} from 'tippy.js';
import 'tippy.js/themes/light-border.css';
import 'tippy.js/animations/fade.css';

tippy('#myButton', {
  render,
  content: document.querySelector('#myPopover'),
  theme: 'light-border',
  animation: 'fade',
  trigger: 'click',
  middleware: [animation, aria, dismiss],
});
```

### Keyboard navigation

If your popover has interactive controls inside of it, such as
buttons or form inputs, read the
[Accessibility Guide](/docs/accessibility#focus-trapping).

## Reusability

It's recommended that you create your own high-level wrappers
around `tippy()` for each floating UI element you make. This way
you don't need to pass all the middleware and props every time
you want to add a popover to your website.

```js
// components/ui/popover.js
import tippy, {render, animation, aria, dismiss} from 'tippy.js';
import 'tippy.js/themes/light-border.css';
import 'tippy.js/animations/fade.css';

export function popover(targets, options = {}) {
  return tippy(targets, {
    render,
    content: options.content ?? '',
    theme: options.theme ?? 'light-border',
    animation: options.animation ?? 'fade',
    trigger: options.trigger ?? 'click',
    interactive: true,
    middleware: [animation, aria, dismiss],
  });
}
```

```js
// app.js
import {popover} from './components/ui/popover';

popover('#add-button', {
  content: document.querySelector('#add-button-popover'),
});
popover('#edit-button', {
  content: document.querySelector('#edit-button-popover'),
});
popover('#delete-button', {
  content: document.querySelector('#delete-button-popover'),
});
```

### Data attributes

<Collapsible>

As seen above, you need to make a new `popover()` call for every
new element if they have different content. You can leverage the
`dataAttributes` middleware to reduce this to a single call:

```js {7,15-17,22}
// components/ui/popover.js
import tippy, {
  render,
  animation,
  aria,
  dismiss,
  dataAttributes,
} from 'tippy.js';
import 'tippy.js/themes/light-border.css';
import 'tippy.js/animations/fade.css';

export function popover(targets, options = {}) {
  return tippy(targets, {
    render,
    content(reference) {
      return document.querySelector(
        reference.getAttribute('data-template')
      );
    },
    theme: options.theme ?? 'light-border',
    animation: options.animation ?? 'fade',
    trigger: options.trigger ?? 'click',
    interactive: true,
    middleware: [animation, aria, dismiss, dataAttributes],
  });
}
```

```html
<button data-template="#add-button-popover">Add</button>
<button data-template="#edit-button-popover">Edit</button>
<button data-template="#delete-button-popover">Delete</button>
```

```js
// app.js
import {popover} from './components/ui/popover';

popover('[data-template]');
```

</Collapsible>

## Complete

You've now built a fully styled, animated, accessible, and
reusable popover component using Tippy.
